from __future__ import absolute_import
import re
import os
from os.path import join

NETWORK_DEVICE_TYPES = ['ethernet', 'wireless']

from forest.registry import register_tool

VALID_MATCH_KEYS = ['class', 'name', 'address', 'order']

__all__ = ['NetworkDevices', 'NetworkDevice', 'LoopbackDevice', 'EthernetDevice', 'WirelessDevice', 'NetworkDeviceMatcher']

@register_tool('network.NetworkDevices')
class NetworkDevices(object):
    ''' Detect all network devices from /sys/class/net
    '''
    def __init__(self):
        self.detect()

    def get_devices(self):
        ''' Returns a list of devices '''
        return self.devices

    def detect(self):

        ifs_path = '/sys/class/net'

        self.devices = []


        for index, iff in enumerate(os.listdir(ifs_path)):

            full_path = join(ifs_path, iff)

            dev = None

            if LoopbackDevice.RE.match(iff) is not None:
                dev = LoopbackDevice(full_path)

            elif EthernetDevice.RE.match(iff) is not None:
                dev = EthernetDevice(full_path)

            elif WirelessDevice.RE.match(iff) is not None:
                dev = WirelessDevice(full_path)

            if dev:
                dev.properties['order'] = str(index)
                self.devices.append(dev)

        self.devices = sorted(self.devices, key=lambda dev: dev.properties.get('bus'))
        print 'Detected %s devices.' % len(self.devices), self.devices

class NetworkDevice(object):
    CLASS = ''
    RE = re.compile('')
    path = None
    def __init__(self, path=None):
        self.path = path

        addrpath = join(path, 'address')

        name = path.split('/')[-1]
        addr = open(addrpath).read()[:-1].strip()

        addrpath = join(path, 'address')
        devpath = join(path, 'device')
        try:
            buspath = os.readlink(devpath)
        except OSError:
            buspath = ''

        self.properties = {
            'class': self.CLASS,
            'name': name,
            'address': addr,
            'bus': buspath
        }

    def get(self, prop):
        if prop not in self.properties:
            raise Exception('Network device does not have property %s' % prop)

        return self.properties[prop]

    def __repr__(self):
        return 'NetworkDevice(%(class)s, %(name)s, %(address)s)' % self.properties


class LoopbackDevice(NetworkDevice):
    CLASS  = 'loopback'
    RE = re.compile('lo.*')

class EthernetDevice(NetworkDevice):
    CLASS  = 'ethernet'
    RE = re.compile('(eth\d{1,}|p(\d)\p(\d){1,}).*')

class WirelessDevice(NetworkDevice):
    CLASS = 'wireless'
    RE = re.compile('wlan{1,}')


@register_tool('network.NetworkDeviceMatcher')
class NetworkDeviceMatcher(object):
    ''' Allows matching devices based on a mini expression language:

        * match: ['name=eth0'] or (not reliable)

        * match: ['address=xx:xx:xx:xx:xx:xx'] or (better)

        * match: ['name=(p6p|eth)\d+'] (matching motherboard or extra ether) or

        * match: ['class=(ethernet|wireless|...)'] (class wildcard)

        * match: ['class=ethernet', 'order=\d+'] (meaning 1st/2nd/3rd/etc of
        device class, can be somewhat reliable as some devices are checked
        first.. although mb manf has discretion)..

        # TODO: any/all/oneof?

    '''
    def __init__(self, network_devices):
        self.devices = network_devices

    def match(self, *expressions):
        eq_expressions = {}
        other_expressions = {}
        # first build the expressions
        for expression in expressions:
            eq_index = expression.find('=')

            if eq_index == -1:
                continue

            key, value = expression[0:eq_index], expression[eq_index + 1:]

            if key not in VALID_MATCH_KEYS:
                raise Exception('Key %s can not be used to match network devices' % key)

            if key in ['order']:
                other_expressions[key] = value
            else:
                eq_expressions[key] = re.compile(value, re.I)

        #matching_devices = []

        #print 'Expressions:', ['%s:%s' % (key, pattern.pattern) for key, pattern in eq_expressions.items()]

        match_set = set(self.devices.get_devices())
        for key, expression in eq_expressions.items():
            matches = set()
            for device in match_set:
                val = device.get(key)
                mat = expression.match(val)

                if mat is not None:
                    #print 'Tested %s: %s' % (key, val), device, expression.pattern
                    matches.add(device)

            if matches:
                match_set = match_set.intersection(matches)
            else:
                match_set = set()

        if 'order' in other_expressions:
            index = int(other_expressions['order'])

            sorted_set = sorted(list(match_set), key=lambda dev: int(dev.properties['order']))
            match_set = [sorted_set[index]]

        return match_set


if __name__ == '__main__':
    devs = NetworkDevices()

    ndm = NetworkDeviceMatcher(devs)

    print 'Matching ether'
    print ndm.match(*['class=ethernet'])
    print 'Matching eth0'
    print ndm.match(*['name=eth0'])
    print 'Matching eth or net format'
    print ndm.match(*['name=(eth\d{1,}|p\d{1,}p\d{1,})'])
    print 'Matching mac'
    print ndm.match(*['address=^00:1e:68:cb:ec:[0-9a-f]{2}$'])
    print 'First ether'
    print ndm.match(*['class=ethernet','order=0'])
